package org.ecjmn.pringles;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Hashtable;

import org.ecjmn.pringles.analysis.DepthFirstAdapter;
import org.ecjmn.pringles.game.*;
import org.ecjmn.pringles.node.*;


public class Interpreter extends DepthFirstAdapter {
	// Hastable to associate integer and boolean actualValues with expression and
	// condition nodes
	Hashtable actualValues = new Hashtable();
	Hashtable symbol_table = new Hashtable();
	
	void setNewSymbol(Node node, String dataType){
		symbol_table.put(node, new String(dataType));	
	}
	
	String getSymbol(Node node){
		return (symbol_table.get(node).toString());
	}
	
	void setIntArrayValue(Node node, int value) {
		actualValues.put(node, new Integer(value));
	}

	// Routines to avoid typecasts
	void setIntValue(Node node, int value) {
		actualValues.put(node, new Integer(value));
	}

	int getIntValue(Node node) {
		return ((Integer) actualValues.get(node)).intValue();
	}

	void setBoolValue(Node node, boolean value) {
		actualValues.put(node, new Boolean(value));
	}

	boolean getBoolValue(Node node) {
		return ((Boolean) actualValues.get(node)).booleanValue();
	}

	// Hastable to hold variables and their actualValues
	Hashtable variables = new Hashtable();

	// Routines to avoid typecasts
	void setVariable(String name, int value) { //a = 1
		variables.put(name.toUpperCase(), new Integer(value));
		//out.print(variables.get('a'));
	}

	int getVariable(String name) {
		Integer variable = (Integer) variables.get(name.toUpperCase());

		// If the variable has already been assigned a value
		if (variable != null)
			return variable.intValue(); // return the stored value
		else
			return 0; // else return 0
	}

	// IF condition THEN statements [ELSE statements] ENDIF
	public void caseAIfStatement(AIfStatement node) {
		node.getCondition().apply(this); // Evaluate the condition

		if (getBoolValue(node.getCondition())) {
			node.getStatements().apply(this); // Evaluate the <then> statements
		} else {
			node.getOptionalElse().apply(this); // Evaluate the <else>
												// statements
		}
	}

	// FOR identifier := from_exp TO to_exp statements NEXT
	public void caseAForStatement(AForStatement node) {
		node.getFromExp().apply(this); // Evaluate the <from> expression
		node.getToExp().apply(this); // Evaluate the <to> expression

		int from = getIntValue(node.getFromExp());
		int to = getIntValue(node.getToExp());

		for (int i = from; i <= to; i++) {
			setVariable(node.getIdentifier().getText(), i); // Assign the value
															// to the loop
															// variable

			node.getStatements().apply(this); // Evaluate the statements
		}
	}

	// INPUT and OUTPUT STREAMS
	BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	PrintStream out = System.out;

	void error(String message) {
		out.println();
		out.println(message);
		System.exit(0);
	}

	// READ identifier
	public void caseAReadStatement(AReadStatement node) {
		System.out.print("? "); // Display a prompt
		try {
			String s = in.readLine(); // Read one line of text
			int value = Integer.parseInt(s); // parse the value
			setVariable(node.getIdentifier().getText(), value); // Set the
																// variable
		} catch (IOException e) // I/O ERROR!!!
		{
			error("I/O ERROR IN LINE " + node.getRead().getLine());
		} catch (NumberFormatException e) // NUMBER FORMAT ERROR!!!
		{
			error("NUMBER FORMAT ERROR IN LINE " + node.getRead().getLine());
		}
	}
	/*
	// PRINT array
	public void caseAPrintArrayStatement(APrintArrayStatement node){
		out.println("AprintArray");
		node.getArray().toString(); // Evaluate the expression
		
		out.print(node.getArray().toString());
		//PExpression type2 = node.getExpression().;
		//on going
		
		out.print(getIntValue(node.getExpression())); // print it
	}
	*/

	// PRINT expression
	public void caseAPrintExpStatement(APrintExpStatement node) {
String expression = node.getExpression().toString().trim();
		
		if (decks.containsKey(expression)) {
			decks.get(expression).print();
		}
		else if (hands.containsKey(expression)) {
			hands.get(expression).print();
		}
		else {
			node.getExpression().apply(this); // Evaluate the expression <-- "player[0]"
			String key = node.getExpression().toString().trim();
			//PExpression type2 = node.getExpression().;
			//on going
			//out.println(key);
			
			out.print(actualValues.get(key)); // print it
		}
	}

	// PRINT string
	public void caseAPrintStrStatement(APrintStrStatement node) {
		String s = node.getString().getText(); // get the string text
		out.print(s.substring(1, s.length() - 1)); // Don't print the ""
	}

	// PRINTLN
	public void caseAPrintlnStatement(APrintlnStatement node) {
		out.println(); // print a new line
	}

	// identifier := expression
	public void caseAAssignmentArrayStatement(AAssignmentArrayStatement node) {
		out.println("[caseAAssignmentArrayStatement]");
		
		node.getIdentifier().apply(this);
		String array = node.getIdentifier().toString().trim();
		out.print("array= "+array);
		
		node.getExp1().apply(this); // Evaluate the expression
		int index = getIntValue(node.getExp1());
		
		node.getExp2().apply(this); // Evaluate the expression
		int dataType = getIntValue(node.getExp2());
		out.print(index+" "+ dataType);
		
		String arrayName = node.getIdentifier().getText() + "[" + index + "]";
		
		//type checking stage
		out.println("[symbol_table.get(array)]test = "+symbol_table.get(array));
		out.println("[symbol_table]"+symbol_table.containsKey(array));
		
		if (symbol_table.containsKey(array)==false)
			out.print("Variable "+array+" unexist, error exit.");
		//else if (dataType == symbol_table.get(array))
		
		
		out.println(index+" "+dataType+" "+array);
		actualValues.put(arrayName, dataType);
		
		
		out.println("id = "+arrayName+", value = "+ actualValues.get(arrayName));
		
	}	
	
	// identifier assign [exp2]:expression termination |
	public void caseAAssignmentIdStatement(AAssignmentIdStatement node) {
		out.println("caseAAssignmentIdStatement");
		node.getIdentifier().apply(this); // Evaluate the expression
		String id = node.getIdentifier().getText().trim();		
		out.println("ID of ID is"+id);

		node.getExp2().apply(this); // Evaluate the expression
		int index = getIntValue(node.getExp2());		
		out.println("ID of ID is"+index);	
		
		
		//type checking stage
		out.println("[symbol_table.get(array)]test = "+symbol_table.get(id));
		out.println("[symbol_table]"+symbol_table.containsKey(id));
		
		if (symbol_table.containsKey(id)==false){
			out.print("Variable "+id+" unexist, error exit.");
			return;
		}
		//else if (dataType == symbol_table.get(array))
		actualValues.put(id, index);
		
		
		//out.println(index+" "+dataType+" "+array);
		//actualValues.put(arrayName, dataType);
		
		
		out.println("id = "+id+", value = "+ actualValues.get(id));
		
	}
	
	/*public void caseADeclarationStatement(ADeclarationStatement node) {
		String id = node.getIdentifier().toString().trim();
		String type = node.getModifier().toString().trim();
		if (node.getModifier().toString().equals("Hand")){
			//create array
			//initialise a hand
		}
		else if  (node.getModifier().toString().equals("Deck")){
			//craete array, initilize 52 cards
		}
		//check if symbol existed or not
		if (symbol_table.containsKey(id)){
			out.println("Identity:["+id+"] already existed.");
			return;
		}
		
		symbol_table.put(id, type);
		out.println("[symbol_table] key= "+id+", value = "+ symbol_table.get(id));
	}*/
	
	Hashtable arrays = new Hashtable();
	
	// array := expression
/*
	public void caseAArrayAssignmentStatement(AArrayAssignmentStatement node) {
		node.getArray(); // Evaluate the expression
		node.getExpression().apply(this);
		node.getAssign().apply(this);
		
		String arrayName = node.getIdentifier().getText() + "[" + node.getArray() + "]";
		out.println(arrayName);
		arrays.put(arrayName, node.getExpression());
		
		//void setVariable(String name, int value) {
		//	variables.put(name.toUpperCase(), new Integer(value));
		//}
		
		out.println(node.getExpression());
		//setVariable( // Assign variable
		//		node.getLeftHandSide().getIdentifier().getText(), getIntValue(node
		//				.getExpression()));
	}
*/
	
	/*
	// eagle testing
	public void caseAArrayAssignmentStatement2 (AArrayAssignmentStatement2 node) {
		node.getArrayId(); // Evaluate the expression
		node.getExpression().apply(this);
		node.getAssign().apply(this);
		
		String arrayName = node.getIdentifier().getText() + "[" + node.getArrayId() + "]";
		out.println(arrayName);
		arrays.put(arrayName, node.getExpression());
		out.println(node.getExpression());
		
	}
*/		
	static final int LT = 0;
	static final int GT = 1;
	static final int EQ = 2;

	boolean evalCondition(Node left, Node right, int operator) {
		left.apply(this); // Evaluate the left expression
		right.apply(this); // Evaluate the right expression
		
		//System.out.println("Left:"+left.toString().trim());
		//System.out.println("Right:"+right.toString().trim());
		
		try {
		    int l = Integer.parseInt(left.toString().trim());
		    int r = Integer.parseInt(right.toString().trim());

			switch (operator) {
			case LT:
				return getIntValue(left) < getIntValue(right);
			case GT:
				return getIntValue(left) > getIntValue(right);
			case EQ:
				return getIntValue(left) == getIntValue(right);
			default:
				throw new RuntimeException("Internal Error");
			}
			
		}
		catch(NumberFormatException nFE) {
			//System.out.println("asdasd");
			int l = (Integer) actualValues.get(left.toString().trim());
		    int r = (Integer) actualValues.get(right.toString().trim());
			switch (operator) {
			case LT:
				return l < r;
			case GT:
				return l > r;
			case EQ:
				return l == r;
			default:
				throw new RuntimeException("Internal Error");
			}
		}
	}

	// left < right
	public void caseALessThanCondition(ALessThanCondition node) {
		setBoolValue(node, evalCondition(node.getLeft(), node.getRight(), LT));
	}

	// left > right
	public void caseAGreaterThanCondition(AGreaterThanCondition node) {
		setBoolValue(node, evalCondition(node.getLeft(), node.getRight(), GT));
	}

	// left = right
	public void caseAEqualCondition(AEqualCondition node) {
		setBoolValue(node, evalCondition(node.getLeft(), node.getRight(), EQ));
	}

	static final int PLUS = 0;
	static final int MINUS = 1;
	static final int MULT = 2;
	static final int DIV = 3;
	static final int MOD = 4;

	int evalExpression(Node left, Node right, int operator) {
		left.apply(this); // Evaluate the left expression
		right.apply(this); // Evaluate the right expression

		switch (operator) {
		case PLUS:
			return getIntValue(left) + getIntValue(right);
		case MINUS:
			return getIntValue(left) - getIntValue(right);
		case MULT:
			return getIntValue(left) * getIntValue(right);
		case DIV:
			return getIntValue(left) / getIntValue(right);
		case MOD:
			return getIntValue(left) % getIntValue(right);
		default:
			throw new RuntimeException("Internal Error");
		}
	}

	// left + right
	public void caseAValueValue2(AValueValue2 node) {
		node.getValue().apply(this); // Evaluate the expression
		
		setIntValue(node, getIntValue(node.getValue()));	}

	// value
	public void caseAValue2Expression(AValue2Expression node) {
		node.getValue2().apply(this); // Evaluate the expression

		setIntValue(node, getIntValue(node.getValue2()));
	}

	// left + right
	public void caseAPlusExpression(APlusExpression node) {
		setIntValue(node, evalExpression(node.getLeft(), node.getRight(), PLUS));
	}

	// left - right
	public void caseAMinusExpression(AMinusExpression node) {
		setIntValue(node,
				evalExpression(node.getLeft(), node.getRight(), MINUS));
	}

	// left * right
	public void caseAMultExpression(AMultExpression node) {
		setIntValue(node, evalExpression(node.getLeft(), node.getRight(), MULT));
	}

	// left / right
	public void caseADivExpression(ADivExpression node) {
		try {
			setIntValue(node, evalExpression(node.getLeft(), node.getRight(),
					DIV));
		} catch (ArithmeticException e) // DIVIDE BY ZERO ERROR
		{
			error("DIVIDE BY ZERO ERROR IN LINE " + node.getDiv().getLine());
		}
	}

	// left MOD right
	public void caseAModExpression(AModExpression node) {
		try {
			setIntValue(node, evalExpression(node.getLeft(), node.getRight(),
					MOD));
		} catch (ArithmeticException e) // MODULUS ZERO ERROR
		{
			error("MODULUS ZERO ERROR IN LINE " + node.getMod().getLine());
		}
	}

	public void caseAConstantValue(AConstantValue node) {
		try {
			int value = Integer.parseInt(node.getNumber().getText().trim()); // parse
																		// the
																		// value
			setIntValue(node, value);
		} catch (NumberFormatException e) // NUMBER FORMAT ERROR!!!
		{
			error("NUMBER FORMAT ERROR IN LINE " + node.getNumber().getLine());
		}
	}

	public void caseAIdentifierValue(AIdentifierValue node) {
		setIntValue(node, getVariable(node.getIdentifier().getText()));
	}

	public void caseAExpressionValue(AExpressionValue node) {
		node.getExpression().apply(this); // Evaluate the expression

		setIntValue(node, getIntValue(node.getExpression()));
	}

	public void caseArray2Array(AArray2Array node) {
	
		out.print(node.getIdentifier());
		out.print(node.getExpression());
		setIntValue(node, getIntValue(node.getExpression()));
	
	}
	
	Hashtable<String,Deck> decks = new Hashtable<String,Deck>();
	Hashtable<String,Hand> hands = new Hashtable<String,Hand>();
	
	public void caseADeclarationStatement(ADeclarationStatement node) {
		String modifier = node.getModifier().toString().trim();
		String identifier = node.getIdentifier().toString().trim();
		
		String id = node.getIdentifier().toString().trim();
		String type = node.getModifier().toString().trim();

		//System.out.println(modifier);
		
		if (modifier.equals("Deck")) {
			Deck deck = new Deck();
			decks.put(identifier, deck);
		}
		else if (modifier.equals("Hand")) {
			Hand hand = new Hand();
			hands.put(identifier, hand);
		}
		else {
			if (node.getModifier().toString().equals("Hand")){
				//create array
				//initialise a hand
			}
			else if  (node.getModifier().toString().equals("Deck")){
				//craete array, initilize 52 cards
			}
			//check if symbol existed or not
			if (symbol_table.containsKey(id)){
				out.println("Identity:["+id+"] already existed.");
				return;
			}
			
			symbol_table.put(id, type);
			out.println("[symbol_table] key= "+id+", value = "+ symbol_table.get(id));
		}
	}
	
	public void caseAPutinExpStatement(APutinExpStatement node) {
		Deck fromDeck = decks.get(node.getId1().toString().trim());
		Hand toHand = hands.get(node.getId2().toString().trim());
		toHand.put(fromDeck.pop());
	}
	
	public void caseAShuffleStatement(AShuffleStatement node) {
		String identifier = node.getIdentifier().toString().trim();
		
		if (decks.containsKey(identifier)) {
			decks.get(identifier).shuffle();
		}
		else if (hands.containsKey(identifier)) {
			hands.get(identifier).shuffle();
		}
		else {
		
		}
	}
	
	public void caseAGetSumSmallStatement(AGetSumSmallStatement node) {
		String valuable = node.getVariable().toString().trim();
		String deck_or_hand = node.getDeckOrHand().toString().trim();
		
		if (decks.containsKey(deck_or_hand)) {
			actualValues.put(valuable, decks.get(deck_or_hand).valueWithAIs1());
		}
		else if (hands.containsKey(deck_or_hand)) {
			actualValues.put(valuable, hands.get(deck_or_hand).valueWithAIs1());
		}
		else {
		
		}
	}
	
	public void caseAGetSumBigStatement(AGetSumBigStatement node) {
		String valuable = node.getVariable().toString().trim();
		String deck_or_hand = node.getDeckOrHand().toString().trim();
		
		if (decks.containsKey(deck_or_hand)) {
			actualValues.put(valuable, decks.get(deck_or_hand).valueWithAIs11());
		}
		else if (hands.containsKey(deck_or_hand)) {
			actualValues.put(valuable, hands.get(deck_or_hand).valueWithAIs11());
		}
		else {
		
		}
	}
	
	public void caseATopValueStatement(ATopValueStatement node) {
		String valuable = node.getVariable().toString().trim();
		String deck_or_hand = node.getDeckOrHand().toString().trim();
		
		if (decks.containsKey(deck_or_hand)) {
			actualValues.put(valuable, decks.get(deck_or_hand).topValue());
		}
		else if (hands.containsKey(deck_or_hand)) {
			actualValues.put(valuable, hands.get(deck_or_hand).topValue());
		}
		else {
		
		}
	}
	
	//infinity loop
	public void caseAInfLoopStatement(AInfLoopStatement node) {
		for (;;) {
			String statement = node.getStatements().toString().trim();
			if (statement.equals("break;")) break;
			if (statement.equals("continue;")) break;
			node.getStatements().apply(this); // Evaluate the statements
		}
	}
}
